//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//

#include "SpeakerHeadset.h"


#ifdef DEBUGMSG
#undef DEBUGMSG
#endif

#define DEBUGMSG RETAILMSG
typedef enum 
{
	CarKitMask = 1,
	HeadsetMask = 2,
	SpeakerphoneMask = 4
} DeviceMask_t;

bool fSpeakerHeadsetInitialized;
CRITICAL_SECTION SPKDeviceLock;
const wchar_t *szHardwareRegRootKey = L"System\\State\\Hardware";

struct SpeakerHeadsetDevice 
{
	unsigned int SpeakerPhoneState;
};

static unsigned int CurrentDriverState = 0;
//volatile SpeakerHeadsetDevice *g_SpeakerHeadsetDevice = NULL;
extern HardwareContext *g_pHWContext;
NotifyWindowList g_HeadsetNotificationList;
NotifyWindowList g_SpeakerPhoneNotificationList;
NotifyWindowList g_CarkitNotificationList;

// Notify the registered clients of the change in the device state.
DWORD NotifyClientsOfStateChange(unsigned __int8 changedStateMask)
{
	unsigned __int8 val = 0;

	NKDbgPrintfW(TEXT("[SPK]++SPK --> NotifyClientsOfStateChange ChangedMask=%d\r\n"), changedStateMask) ;
	if (changedStateMask & CarKitMask) {
		val = (CurrentDriverState & CarKitMask)?1:0;
		g_CarkitNotificationList.SendMessage(val);
	}

	if (changedStateMask & HeadsetMask) {
		val = (CurrentDriverState & HeadsetMask)?1:0;
		g_HeadsetNotificationList.SendMessage(val);
	}

	if (changedStateMask & SpeakerphoneMask) {
		val = (CurrentDriverState & SpeakerphoneMask)?1:0;
		g_SpeakerPhoneNotificationList.SendMessage(val);
	}
	NKDbgPrintfW(TEXT("[SPK]--SPK --> NotifyClientsOfStateChange\r\n"));
	return 0;
}

DWORD UpdateRegistryValues(unsigned __int8 changedValMask)
{
	HKEY hHardwareKey = NULL;
	DWORD val;	
	DWORD error = 0;

	NKDbgPrintfW(TEXT("[SPK]++SPK --> UpdateRegistryValues ChangedMask=%d\r\n"), changedValMask);
	if (ERROR_SUCCESS == (error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szHardwareRegRootKey, 0, 0, &hHardwareKey))) {
		if (changedValMask & CarKitMask) {
			val = CurrentDriverState & CarKitMask;
			if (ERROR_SUCCESS != RegSetValueEx(hHardwareKey, L"Car Kit", 0, REG_DWORD, (const BYTE*)&val, sizeof(DWORD))) {
				error = GetLastError();
			}
		}

		if (changedValMask & HeadsetMask) {
			val = (CurrentDriverState & HeadsetMask)?1:0;
			if (ERROR_SUCCESS != RegSetValueEx(hHardwareKey, L"Headset", 0, REG_DWORD, (const BYTE*)&val, sizeof(DWORD))) {
				error = GetLastError();
			}
		}
		
		if (changedValMask & SpeakerphoneMask) {
			val = (CurrentDriverState & SpeakerphoneMask)?1:0;
			if (ERROR_SUCCESS != RegSetValueEx(hHardwareKey, L"Speaker", 0, REG_DWORD, (const BYTE*)&val, sizeof(DWORD))) {
				error = GetLastError();
			}			 
		}
		RegCloseKey(hHardwareKey);
	} else {
		error = GetLastError();
	}
	NKDbgPrintfW(TEXT("[SPK]--SPK --> UpdateRegistryValues %d\r\n"), error);
	return error;
}

/******************************************
This function reads the current state of the 
device and verifies with the stored state, if 
a difference is there in stored state and current
state, the device status is updated and a 
notification is sent to all the registered 
window handles. Please make sure that you take lock
as this modifies the datastructures.
**********************************************/

DWORD ProcessMaskChange()
{
	DWORD error = 0;
	unsigned __int8 deviceState = 0;
	unsigned __int8 changedMask = 0;
   
	NKDbgPrintfW(TEXT("++SPK --> ProcessMaskChange\r\n"));


	
	// Check if device state and driver state are different.
	if (deviceState != CurrentDriverState) {
		changedMask = deviceState ^ CurrentDriverState;
		CurrentDriverState = deviceState;
		error = NotifyClientsOfStateChange(changedMask);		
		error = UpdateRegistryValues(changedMask);
	}

	NKDbgPrintfW(TEXT("--SPK --> ProcessMaskChange Error=%d\r\n"), error);
	return error;
}

/************************************************
This is just a wrapper over ProcessMaskChange but 
takes a lock. This is called from EmulServ device.
*************************************************/
void ProcessSPKMaskChange()
{
	if (fSpeakerHeadsetInitialized) {
		EnterCriticalSection(&SPKDeviceLock);
		ProcessMaskChange();
		LeaveCriticalSection(&SPKDeviceLock);	 
	}
}
	

BOOL SetSpeakerPhoneMode(unsigned __int8 newSpeakerMode)
{
	BOOL success = TRUE;
	unsigned __int8 maskVal;
	
	// wparam should be 1 or 0. Any other value is taken as invalid parameter.
	if (newSpeakerMode != 1 && newSpeakerMode != 0) {
		SetLastError(ERROR_INVALID_PARAMETER);
		success = FALSE;
	} else {
		if (newSpeakerMode) {
			maskVal = SpeakerphoneMask;
			g_pHWContext->SetCfgSpeakerPhone(TRUE);
		} else {
			maskVal = 0;
			g_pHWContext->SetCfgSpeakerPhone(FALSE);
		}				 
		// Write the value into the device.
		// Any other value written other than this mask will be taken as invalid and emulator will terminate.

		// Read the state from device.

		success = g_SpeakerPhoneNotificationList.SendMessage(newSpeakerMode);
		UpdateRegistryValues(maskVal);
	}
	return success;
}

extern "C" DWORD SPK_Init(LPCTSTR pContext, DWORD dwBusContext)
{  
	
	DWORD error = 0;

	NKDbgPrintfW(TEXT("[SPK]++SPK_Init\r\n"));
	InitializeCriticalSection(&SPKDeviceLock);

	EnterCriticalSection(&SPKDeviceLock);
	// Reset the driver state.
	CurrentDriverState = 0;
	g_HeadsetNotificationList.ResetRecordList();
	g_SpeakerPhoneNotificationList.ResetRecordList();
	g_CarkitNotificationList.ResetRecordList();
	



	// Now go to the actual address of the device.
	//g_SpeakerHeadsetDevice = (volatile SpeakerHeadsetDevice*)((size_t)g_SpeakerHeadsetDevice ;
	// Carry out the initialization.
	error = ProcessMaskChange();

	LeaveCriticalSection(&SPKDeviceLock);
	if (error == 0) {
		fSpeakerHeadsetInitialized = true;
	}
	NKDbgPrintfW(TEXT("[SPK]--SPK_Init Error=%d\r\n"), error);
	return 1;
}

extern "C" BOOL SPK_Deinit(DWORD hDeviceContext)
{
	BOOL success = FALSE;
	NKDbgPrintfW(TEXT("++SPK_Deinit\r\n"));
	EnterCriticalSection(&SPKDeviceLock);

	// Reset the driver state.
	CurrentDriverState = 0;
	g_HeadsetNotificationList.ResetRecordList();
	g_SpeakerPhoneNotificationList.ResetRecordList();
	g_CarkitNotificationList.ResetRecordList();    
	LeaveCriticalSection(&SPKDeviceLock);
	fSpeakerHeadsetInitialized = false;
	DeleteCriticalSection(&SPKDeviceLock);
	NKDbgPrintfW(TEXT("--SPK_Deinit\r\n"));
	return success;
}

extern "C" void SPK_PowerUp(DWORD hDeviceContext) 
{
	NKDbgPrintfW(TEXT("++SPK_PowerUp\r\n"));
	EnterCriticalSection(&SPKDeviceLock);
	// It is the restore-state scenrio. During the restore-state, update device appropriately.
	ProcessMaskChange();
	LeaveCriticalSection(&SPKDeviceLock);
	NKDbgPrintfW(TEXT("--SPK_PowerUp\r\n"));
}

extern "C" BOOL SPK_IOControl(DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
{
	DWORD error = 0;
	DEVNOTIFY_EVENT tempEv;
	NOTIFY_NODE* pTempNode = NULL;

	NKDbgPrintfW(TEXT("++SPK_IOControl dwCode=%d\r\n"), dwCode);
	if (!hOpenContext) {
		SetLastError(ERROR_INVALID_HANDLE);
		error = ERROR_INVALID_HANDLE;
		NKDbgPrintfW(TEXT("--SPK_IOControl %d\r\n"), error);
		return FALSE;
	}

	BOOL success = TRUE;
	DWORD dwIndex = -1;
	HWND hWnd = NULL;
	EnterCriticalSection(&SPKDeviceLock);

	// Check and execute dwCode.
	switch (dwCode)
	{
		case IOCTL_HEADSET_ADD_NOTIFY_HWND:
			// pBufIn should contain the pointer.
			if (NULL == pBufIn || (dwLenIn != sizeof(DEVNOTIFY_EVENT))) {
				ASSERT(0);
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}
			__try {
				tempEv = *((DEVNOTIFY_EVENT*)pBufIn);
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				ASSERT(0);
				SetLastError(ERROR_ACCESS_DENIED);
				success = FALSE;
				goto Done;
			}
			
			if (!(pTempNode = g_HeadsetNotificationList.AddWindow(tempEv))) {
				SetLastError(ERROR_NOT_ENOUGH_MEMORY);
				success = FALSE;
				goto Done;
			} else {		  
				// If Headset is already plugged in, send a notification.
				if (CurrentDriverState & HeadsetMask) {
					g_HeadsetNotificationList.SendMessage(pTempNode, 1);
				}
			}
			break;
		case IOCTL_HEADSET_REMOVE_NOTIFY_HWND:
			// pBufIn should contain the valid pointer.
			if (NULL == pBufIn || dwLenIn != sizeof (HWND)) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}

			__try {
				hWnd = *(HWND*)pBufIn;
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				ASSERT(0);
				success = FALSE;
				SetLastError (ERROR_ACCESS_DENIED);
				goto Done;
			}

			// Remove the handle from the notification list.
			if (!g_HeadsetNotificationList.RemoveWindow(hWnd)) {
				SetLastError(ERROR_INVALID_HANDLE);
				success = FALSE;
				goto Done;
			}
			break;

		case IOCTL_SPEAKER_ADD_NOTIFY_HWND:
			if (NULL == pBufIn || (dwLenIn != sizeof (DEVNOTIFY_EVENT))) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}			 

			__try {
				tempEv = *((DEVNOTIFY_EVENT*)pBufIn);
			} __except (EXCEPTION_EXECUTE_HANDLER) { 
				ASSERT(0);
				success = FALSE;
				SetLastError(ERROR_ACCESS_DENIED);
				goto Done;
			}

			if (!(pTempNode = g_SpeakerPhoneNotificationList.AddWindow(tempEv))) {
				SetLastError(ERROR_NOT_ENOUGH_MEMORY);
				success = FALSE;
				goto Done;
			} else {
				// Send a message to window if speakerphone is already on.
				if (CurrentDriverState & SpeakerphoneMask) {
					g_SpeakerPhoneNotificationList.SendMessage(pTempNode, 1);
				}
			}
			break;
		case IOCTL_SPEAKER_REMOVE_NOTIFY_HWND:
			if (NULL == pBufIn || (dwLenIn != sizeof (HWND))) { 
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}

			__try {
				hWnd = *((HWND*)pBufIn);
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				ASSERT(0);
				SetLastError (ERROR_ACCESS_DENIED);
				success = FALSE;
				goto Done;
			}

			if (!g_SpeakerPhoneNotificationList.RemoveWindow(hWnd)) {
				SetLastError(ERROR_INVALID_HANDLE);
				success = FALSE;
				goto Done;
			}

			break;
		case IOCTL_SPEAKER_GETMODE:
			if (NULL == pBufOut || dwLenOut != sizeof(CurrentDriverState)) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}

			__try {
				*(int*)pBufOut = (CurrentDriverState & SpeakerphoneMask)?1:0;
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				SetLastError(ERROR_ACCESS_DENIED);
				success = FALSE;
				goto Done;
			}
			break;
		case IOCTL_SPEAKER_SETMODE:
			if (NULL == pBufIn || dwLenIn != sizeof(CurrentDriverState)) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				NKDbgPrintfW(TEXT("[SPK]Set Speaker Mode Error 1\r\n"));
				NKDbgPrintfW(TEXT("[SPK]Set Speaker Mode: pBufin %x\r\n"), *pBufIn);
				NKDbgPrintfW(TEXT("[SPK]Set Speaker Mode: dwLenIn %d\r\n"), dwLenIn);
				goto Done;
			}

			__try {
				success = SetSpeakerPhoneMode(*(unsigned __int8*)pBufIn);
				
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				SetLastError(ERROR_ACCESS_DENIED);
				success = FALSE;
				NKDbgPrintfW(TEXT("[SPK]Set Speaker Mode Error 2\r\n"));
				goto Done;
			}
			break;
		case IOCTL_CARKIT_ADD_NOTIFY_HWND:
			if (NULL == pBufIn || dwLenIn != sizeof(DEVNOTIFY_EVENT)) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}

			__try {
				tempEv = *((DEVNOTIFY_EVENT*)pBufIn);
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				ASSERT(0);
				SetLastError (ERROR_ACCESS_DENIED);
				success = FALSE;
				goto Done;
			}

			if (!(pTempNode = g_CarkitNotificationList.AddWindow(tempEv))) {
				SetLastError(ERROR_NOT_ENOUGH_MEMORY);
				success = FALSE;
				goto Done;
			} else {
				// See if carkit is already plugged-in.
				if (CurrentDriverState & CarKitMask) {
					g_CarkitNotificationList.SendMessage(pTempNode, 1);
				}
			}
			break;
		case IOCTL_CARKIT_REMOVE_NOTIFY_HWND:
			if (NULL == pBufIn || dwLenIn != sizeof(HWND)) {
				SetLastError(ERROR_INVALID_PARAMETER);
				success = FALSE;
				goto Done;
			}

			__try {
				hWnd = *((HWND*)pBufIn);
			} __except (EXCEPTION_EXECUTE_HANDLER) {
				ASSERT(0);
				SetLastError (ERROR_ACCESS_DENIED);
				success = FALSE;
				goto Done;
			}

			if (!g_CarkitNotificationList.RemoveWindow(hWnd)) {
				SetLastError(ERROR_INVALID_HANDLE);
				success = FALSE;
				goto Done;
			}
			break;
		case IOCTL_PSL_NOTIFY: // Some process which opened the file is exiting without closing the handle gracefully.
			// What to do here.... How do we determine which window handle is invalid now.
			NKDbgPrintfW(TEXT("[SPK]SPK IOControl Process is exiting\r\n"));
			break;
		default:
			SetLastError(ERROR_INVALID_PARAMETER);
			success = FALSE;
			goto Done;
			break;
	}

Done:
	NKDbgPrintfW(TEXT("[SPK]--SPK_IOControl Error=%d\r\n"), error);
	LeaveCriticalSection(&SPKDeviceLock);
	return success;
}	  
			
extern "C" void SPK_PowerDown(DWORD /*hDeviceContext*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_PowerDown\n--SPK_PowerDown\r\n"));
	return;
}

extern "C" DWORD SPK_Open(DWORD /*hDeviceContext*/, DWORD /*AccessMode*/, DWORD /*ShareMode*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_Open\n--SPK_Open\n"));
	return 1;
}

BOOL SPK_Close(DWORD /*hOpenContext*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_Close\n--SPK_Close\n"));
	return TRUE;
}

DWORD SPK_Read(DWORD /*hOpenContext*/, LPVOID /*pBuffer*/, DWORD /*Count*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_Read\n--SPK_Read\n"));
	return 0;
}

DWORD SPK_Write(DWORD /*hOpenContext*/, LPCVOID /*pBuffer*/, DWORD /*Count*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_Write\n--SPK_Write\n"));
	return 0;
}

DWORD SPK_Seek(DWORD /*hOpenContext*/, long /*Amount*/, WORD /*Type*/)
{
	NKDbgPrintfW(TEXT("[SPK]++SPK_Seek\n--SPK_Seek\n"));
	return 0;
}



